"""提供调查相关功能的服务模块."""
from typing import List, Tuple

from cachetools import LRUCache, cached
from cachetools.keys import hashkey
from fastapi import Request
from sqlalchemy import select

from core.database import db_session
from core.exception import AlertException, AlertCloseException
from core.models import Member, Poll, PollEtc
from lib.common import get_client_ip
from service import BaseService


class PollService(BaseService):
    """
    这是一类输液的标的物，提供与调查相关的服务。.
    """

    def __init__(self, request: Request, db: db_session):
        self.request = request
        self.db = db
        self.poll = None

    def raise_exception(self, status_code: int = 400, detail: str = None, url: str = None):
        if url:
            raise AlertException(detail, status_code, url)
        raise AlertCloseException(detail, status_code)

    def create_poll_etc(self, poll: Poll, member: Member, **kwargs) -> PollEtc:
        """
        生成调查其他信息.
        """
        if member:
            kwargs.update(pc_name=member.mb_name)

        poll_etc = PollEtc(
            po_id=poll.po_id,
            mb_id=(member.mb_id if member else ''),
            **kwargs
        )
        self.db.add(poll_etc)
        self.db.commit()
        self.db.refresh(poll_etc)

        return poll_etc

    def update_poll(self, poll: Poll, item: int, member: Member = None) -> Poll:
        """加入调查

        Args:
            poll (Poll): 有关调查的信息
            item (int): 调查项目编号
            member (Member, optional): 会员信息. Defaults to None.

        Returns:
            Poll: 有关调查的信息
        """
        # 更新调查参与信息
        if member:
            mb_id = member.mb_id
            poll.mb_ids = f"{poll.mb_ids},{mb_id}" if poll.mb_ids else mb_id
        else:
            ip = get_client_ip(self.request)
            poll.po_ips = f"{poll.po_ips},{ip}" if poll.po_ips else ip

        # 关于调查项目 1 增加
        setattr(poll, f"po_cnt{item}", getattr(poll, f"po_cnt{item}", 0) + 1)
        self.db.commit()
        self.db.refresh(poll)

        return poll

    def fetch_poll(self, po_id: int) -> Poll:
        """在数据库中查找调查信息."""
        return self.db.get(Poll, po_id)

    def read_poll(self, po_id: int) -> Poll:
        """我们查找调查信息."""
        poll = self.fetch_poll(po_id)
        if not poll:
            self.raise_exception(status_code=404, detail="无调查信息.")
        return poll

    def fetch_other_polls(self, po_id: int) -> List[Poll]:
        # “查看其他调查信息列表。”
        return self.db.scalars(
            select(Poll)
            .where(Poll.po_id != po_id, Poll.po_use == 1)
            .order_by(Poll.po_id.desc())
        ).all()

    def calculate_poll_result(self, poll: Poll) -> Tuple[int, List[dict]]:
        """
            查询调查结果。

            Returns:
            Tuple[int，List[dict]]：投票总数，调查项目列表
        """
        #仅将已存在的调查项目转换为list
        items = [
            {
                "subject": getattr(poll, f"po_poll{i}"),
                "count": getattr(poll, f"po_cnt{i}", 0),
            }
            for i in range(1, 10) if getattr(poll, f"po_poll{i}", None)
        ]
        total_count = sum(item["count"] for item in items)

        # 计算每个调查项目的比例和排名
        for item in items:
            item["rank"] = sum(1 for i in items if i["count"] > item["count"]) + 1
            item["rate"] = round(item["count"] / total_count * 100, 1) if total_count > 0 else 0

        return total_count, items

    def read_poll_etc(self, pc_id: int) -> PollEtc:
        """
        查询其他评论。
        """
        poll_etc = self.db.get(PollEtc, pc_id)
        if not poll_etc:
            self.raise_exception(status_code=404, detail="无其他意见信息.")
        return poll_etc

    def delete_poll_etc(self, poll_etc: PollEtc) -> None:
        """
        删除其他评论.
        """
        self.db.delete(poll_etc)
        self.db.commit()

    @cached(LRUCache(maxsize=1), key=lambda _: hashkey("latest_poll"))
    def fetch_latest_poll(self):
        """
        最新调查集使用 1我抬头看枪。.
        """
        latest_poll = self.db.scalar(
            select(Poll)
            .where(Poll.po_use == 1)
            .order_by(Poll.po_id.desc())
        )
        return latest_poll


class ValidatePollService(BaseService):
    """
    它是一个字幕注入类，提供与调查相关的验证.
    """

    def __init__(self, request: Request, db: db_session):
        self.request = request
        self.db = db

    def raise_exception(self, status_code: int, detail: str = None, url: str = None):
        if url:
            raise AlertException(detail, status_code, url)
        raise AlertCloseException(detail, status_code)

    def check_level(self, poll: Poll, member: Member = None) -> bool:
        """
        检查测量权限级别.
        """
        member_level = getattr(member, "mb_level", 1)
        if member_level < poll.po_level:
            self.raise_exception(
                status_code=403,
                detail=f"权利 {poll.po_level} 只有上面的成员才能访问."
            )

    def is_used(self, poll: Poll) -> bool:
        """
        检查是否设置了调查.
        """
        if poll.po_use != 1:
            self.raise_exception(status_code=403, detail="不是正在进行的调查.")

    def is_participated(self, poll: Poll, member: Member = None) -> bool:
        """
        检查您是否已经是调查的成员.
        """
        ip = get_client_ip(self.request)
        mb_id = getattr(member, "mb_id", None)
        po_ip_list = poll.po_ips.split(",") if poll.po_ips else []
        mb_id_list = poll.mb_ids.split(",") if poll.mb_ids else []

        if (ip in po_ip_list) or (mb_id in mb_id_list):
            self.raise_exception(
                status_code=409,
                detail=f"{poll.po_subject} 您已参与调查.",
                url=f"/bbs/poll_result/{poll.po_id}"
            )

    def is_used_etc(self, poll: Poll) -> bool:
        """
        检查调查是否收到其他评论.
        """
        if not poll.po_etc:
            self.raise_exception(
                status_code=400,
                detail="该调查不使用任何其他意见."
            )

    def is_etc_owner(self, poll_etc: PollEtc, member: Member) -> bool:
        """
        检查您是否是其他评论的作者.
        """
        if poll_etc.mb_id != member.mb_id and member.mb_level != 10:
            self.raise_exception(
                status_code=403,
                detail="只有作者可以删除.")
